/**
 * 堆栈准备
 *  + JS执行环境（V8引擎等）
 *  + 执行环境栈（ECStack，execution context stack），浏览器渲染界面时在内存中开辟的一块空间
 *  + 执行上下文，管理不用的区域，每个执行上下文中的代码需要执行时，进栈执行，栈底永远都有一个全局的执行上下文EC(G)
 *  + VO(G)，全局变量对象
 */

// 1、创建一个值100，由于100是一个基本数据类型，所以会直接存放在栈区
// 2、声明一个变量x，存放在VO(G)
// 3、建立变量与值之间的联系 x = 100
var x = 100
// 1、声明一个变量y，存放在VO(G)
// 2、建立变量与值之间的联系 y = x = 100
var y = x
// 1、创建一个值200，由于200是一个基本数据类型，所以会直接存放在栈区
// 2、断掉y和100的联系
// 2、建立变量与值之间的联系 y = 200
y = 200
console.log(x)

/**
 * 基本数据类型处理
 *  + 基本数据类型是按值进行操作
 *  + 基本数据类型值是存放在栈区的，不会分配16进制的内存地址，也没有开辟内存空间
 *  + 无论是栈内存还是引用类型数据的堆内存，都是属于计算机内存
 *  + GO全局对象
 *    + 它不是VO(G)，但是它也是一个对象，因此它也会有一个内存的空间地址，有地址就可以对其进行访问，浏览器刚要加载某个界面时，会从内存里单独分配一块空间给GO全局对象（可以理解为window对象，JS会在VO(G)当中准备一个变量叫window的引用地址，以便可以直接使用window对象）
 */

/**
 * 引用类型堆栈处理
 *  + 基本数据类型是按值进行操作
 *  + 基本数据类型值是存放在栈区的，不会分配16进制的内存地址，也没有开辟内存空间
 *  + 无论是栈内存还是引用类型数据的堆内存，都是属于计算机内存
 *  + GO全局对象
 *    + 它不是VO(G)，但是它也是一个对象，因此它也会有一个内存的空间地址，有地址就可以对其进行访问，浏览器刚要加载某个界面时，会从内存里单独分配一块空间给GO全局对象（可以理解为window对象，JS会在VO(G)当中准备一个变量叫window的引用地址，以便可以直接使用window对象）
 */

// 1、声明一个变量obj1，存放在VO(G)
// 2、创建一个堆内存heap 0x000，存放对象键值对形式数据，x: 100
// 3、栈区的obj1指向堆内存地址0x000
var obj1 = { x: 100 }
// 栈区的obj2指向obj1同样的堆内存地址0x000
var obj2 = obj1
// 栈区的obj2修改堆内存地址0x000的键x的值为200
obj2['x'] = 200
// 此时0x000地址内的x变成了200，所以obj1打印出来的x为200
console.log(obj1.x)

// 1、声明一个变量obj3，存放在VO(G)
// 2、创建一个堆内存heap 0x000，存放对象键值对形式数据，x: 100
// 3、栈区的obj3指向堆内存地址0x000
var obj3 = { x: 100 }
// 栈区的obj4指向obj3同样的堆内存地址0x000
var obj4 = obj3
// obj4创建并指向一个堆内存heap 0x001，存放对象键值对形式数据，name: tom
obj4 = { name: 'tom' }
// 此时0x000地址内的数据并没有被修改，所以obj3打印出来的x还是100
console.log(obj3.x)

// 1、声明一个变量obj5，存放在VO(G)
// 2、创建一个堆内存heap 0x000，存放对象键值对形式数据，x: 100
// 3、栈区的obj5指向堆内存地址0x000
var obj5 = { x: 100 }
// 栈区的obj6指向obj5同样的堆内存地址0x000
var obj6 = obj5
// 1、obj5.y由于优先运算，在0x000中创建一个y，这个y将来会指向接下来将会创建的内存地址
// 2、obj5 = { x: 200 }，此时obj5创建并指向一个堆内存heap 0x001，存放对象键值对形式数据，x: 200
// 3、此时heap 0x000内存内新增了y指向0x001内存内数据{ x: 200 }
obj5.y = obj5 = { x: 200 }
// 此时obj5内只有x，没有y，输出undefined
console.log(obj5.y)
// 此时obj6地址还是0x000，输出{ x: 200 }
console.log(obj6)

/**
 * 函数类型堆栈处理
 *  + 基本数据类型是按值进行操作
 *  + 基本数据类型值是存放在栈区的，不会分配16进制的内存地址，也没有开辟内存空间
 *  + 无论是栈内存还是引用类型数据的堆内存，都是属于计算机内存
 *  + GO全局对象
 *    + 它不是VO(G)，但是它也是一个对象，因此它也会有一个内存的空间地址，有地址就可以对其进行访问，浏览器刚要加载某个界面时，会从内存里单独分配一块空间给GO全局对象（可以理解为window对象，JS会在VO(G)当中准备一个变量叫window的引用地址，以便可以直接使用window对象）
 * 
 * 函数创建
 *  + 可以将函数名称看作是变量，存放在VO中，同时它的值就是当前函数对应的内存地址
 *  + 函数本身也是一个对象，创建时会有一个内存地址，空间内存放的就是函数体代码（字符串形式）
 * 
 * 函数执行
 *  + 函数执行时会形成一个全新私有上下文，它里面有一个AO（全局执行环境里面叫VO），用于管理这个上下文当中的变量
 *  + 步骤：
 *    + 确定作用域链<当前执行上下文，上级作用域所在的执行上下文>
 *    + 确定this
 *    + 初始化arguments（对象）
 *    + 形参赋值：它就相当于是变量声明，然后将声明的变量放置于AO
 *    + 变量提升
 *    + 代码执行
 */

// 1、声明一个变量arr，存放在VO(G)
// 2、创建一个堆内存heap 0x001，存放对象键值对形式数据，0: zce，1: tom
// 3、栈区的arr指向堆内存地址0x001
var arr = ['zce', 'tom']
// 1、创建函数和创建变量类型，函数名此时就可以看做是一个变量
// 2、单独开辟一个堆内存用于存放函数体（字符串形式代码），当前内存地址也会有一个16进制数值地址0x000（函数创建优先）
// 3、创建函数的时候，它的作用域[[scope]]就已经确定了（创建函数时所在的执行上下文），此例为EC(G)
// 4、创建函数之后会将它的内存地址存放在栈区与对应的函数名进行关联
function foo (obj) {
  // 函数参数obj指向0x001的地址，修改0x001地址上0: zoe
  obj[0] = 'zoe'
  // 重新创建一个堆地址0x002，0: javascript，obj指向0x002
  obj = ['javascript']
  // 0x002地址下新增1: typescript
  obj[1] = 'typescript'
  // 打印0x002地址数据 ['javascript', 'typescript']
  console.log(obj)
}
// 函数执行，目的就是为了将函数对应的堆内存里的字符串形式代码进行执行。代码在执行的时候肯定需要一个环境，此时就意味着函数在执行的时候会生成一个新的执行上下文EC(foo)来管理函数体当中的代码
// 执行函数相当于找到foo的内存地址0x000，然后吧参数的内存地址0x001传入，执行0x000(0x001)
// 函数执行时做的事情
// 1、确定作用域链：<当前执行上下文，上级执行上下文>
// 2、确定this，此例为window
// 3、初始化arguments对象
// 4、行参赋值：obj = arr
// 5、变量提升
// 6、执行代码
foo(arr) // 执行完成后，没有其他引用关系，出栈
// 打印0x001地址数据 [‘zoe’, 'tom']
console.log(arr)

/**
 * 闭包类型堆栈处理
 *  + 闭包是一种机制，通过私有上下文来保护当中变量的机制
 *  + 我们也可以认为当我们创建的某一个执行上下文不被释放的时候就形成来闭包
 *  + 保护：使当前上下文中的数据和其他上下文中的数据互不干扰，保存数据：当前上下文的数据（堆内存）被当前上下文以外的上下文中的变量所引用，这个数据就保存下来
 *  + 函数调用形成来一个全新的私有上下文，在函数调用之后当前上下文不被释放或临时不被释放就是闭包
 * 
 * 闭包与垃圾回收
 *  + 浏览器都自由垃圾回收（内存管理、V8）
 *  + 栈空间和堆空间释放
 *  + 堆：当前堆内存如果被占用，就能被释放掉，但是我们如果确认后续不再使用这个内存里的数据，也可以自己主动置空（将函数设置为null），然后浏览器就会对其进行回收
 *  + 栈：当前上下文中是否有内容，被其他上下文的变量所占用，如果有则无法释放（闭包）
 */

// 全局执行上下文中的VO中声明a = 1
var a = 1
// 为函数foo创立一个堆地址0x000，全局执行上下文中的VO中声明foo指向0x000，确定作用域为EC(G)
// foo函数执行时，会创立一个EC(FOO)的执行上下文，并在其中的AO中声明变量b=2
function foo () {
  var b = 2
  // 函数执行完时返回了一个函数。此时创建一个新的存放此匿名函数的堆地址0x001
  return function (c) {
    console.log(c + b++)
  }
}
// 当前ec(foo)执行上下文当中引用的一个堆地址0x001被ec(g)中的变量f引用，因此foo()调用时所创建的执行上下文不鞥被释放，此时就形成了闭包
// foo函数执行完毕返回了0x001地址的函数，所以f指向0x001
var f = foo()
// f函数执行时创建新的执行上下文ec(f)，并在其AO中声明参数c=5，在上级作用域中找到b，打印5+2=7，b自增为3
f(5)
// 函数每调用一次会产生一个全新的上下文，此时函数执行产生新的执行上下文ec(f)，并在其AO中声明参数c=10，在上级作用域中找到b，打印10+3=13，b自增为4
f(10)
